//
// Created by martin on 8/3/21.
//

#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <event2/event.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>

#include "error.h"
#include "socket.h"
#include "cmdaction.h"
#include "LISTaction.h"
#include "RETRaction.h"
#include "STORaction.h"
#include "STOUaction.h"
#include "APPEaction.h"

#define USER_ANONYMOUS_EN 1   /* 匿名登录 */
#define USER_NEED_PASS_EN 1   /* 无密码登录 */

static void action_user(Session *sess);
static void action_pass(Session *sess);
static void action_cwd(Session *sess);
static void action_cdup(Session *sess);
static void action_quit(Session *sess);

static void action_port(Session *sess);
static void action_pasv(Session *sess);
static void action_type(Session *sess);

static void action_size(Session *sess);
static void action_rnfr(Session *sess);
static void action_rnto(Session *sess);
static void action_dele(Session *sess);
static void action_rmd(Session *sess);
static void action_mkd(Session *sess);
static void action_retr(Session *sess);
static void action_stor(Session *sess);
static void action_stou(Session *sess);
static void action_appe(Session *sess);
static void action_rest(Session *sess);
static void action_abor(Session *sess);
static void action_pwd(Session *sess);
static void action_list(Session *sess);
static void action_syst(Session *sess);
static void action_help(Session *sess);
static void action_noop(Session *sess);


/**
 * 用户请求命令-响应动作映射表
 * @note 列出了RFC 959规定的所有命令，不支持的命令，对应action为NULL
 */
const FtpCmdAction cmd_action[] = {
        /* 访问控制命令 */
        {"USER", action_user},
        {"PASS", action_pass},
        {"ACCT", NULL},
        {"CWD",  action_cwd},
        {"CDUP", action_cdup},
        {"SMNT", NULL},
        {"REIN", NULL},
        {"QUIT", action_quit},

        /* 传输参数命令 */
        {"PORT", action_port},
        {"PASV", action_pasv},
        {"TYPE", action_type},
        {"STRU", NULL},
        {"MODE", NULL},

        /* FTP服务命令 */
        {"SIZE", action_size},
        {"RETR", action_retr},
        {"STOR", action_stor},
        {"STOU", action_stou},
        {"APPE", action_appe},
        {"ALLO", NULL},
        {"REST", action_rest},
        {"RNFR", action_rnfr},
        {"RNTO", action_rnto},
        {"ABOR", NULL},
        {"DELE", action_dele},
        {"RMD", action_rmd},
        {"XRMD", action_rmd},
        {"MKD", action_mkd},
        {"XMKD", action_mkd},
        {"PWD",  action_pwd},
        {"LIST", action_list},
        {"NLST", action_list},
        {"SITE", NULL},
        {"SYST", action_syst},
        {"STAT", NULL},
        {"HELP", NULL},
        {"NOOP", NULL},

        // 终结项，不可省略
        {NULL, NULL}
};

/**
 * 用户命令
 * @param sess
 */
static void action_user(Session *sess)
{
    if (!sess || !sess->bev) return;

    strncpy(sess->user.username, sess->cmd_text, sizeof(sess->user.username));

#if USER_NEED_PASS_EN == 0
    // 如果支持匿名登录，则应答: 230 Login success.
    send_reply(sess->bev, 230, "Login success.");
#else
    // 如果需要密码，则应答：331(or 332) Need password.
    send_reply(sess->bev, 331, "Need password.");
#endif
}

/**
 * 密码命令
 * @param sess
 */
static void action_pass(Session *sess)
{
    if (!sess || !sess->bev) return;

#if USER_ANONYMOUS_EN
    if (strcmp(sess->user.username, "anonymous") == 0) { // 匿名用户，密码任意
        send_reply(sess->bev, 230, "Login success.");
        return;
    }
#else
    //TODO 根据支持的用户名、密码，校验用户登录信息
    send_reply(sess->bev, 230, "Login success.");
#endif
}

/**
 * 获得指定文件大小命令
 * @param sess
 */
static void action_size(Session *sess)
{
    if (!sess || !sess->bev) return;
    char text[100];

    // 检查文件是否存在
    // 参数可能情况：路径（以"/"开头），或文件名（非以"/"开头）
    if (dir_exist(sess->dir, sess->cmd_text)) { // 文件存在
        if (dir_isDir(sess->dir, sess->cmd_text)) { // 文件是目录
            send_reply(sess->bev, 550, "Access is denied.");
            return;
        }

        // 获取文件大小
        ssize_t size = dir_getFileSize(sess->dir, sess->cmd_text);
        if (size > 0) {
            snprintf(text, sizeof(text), "%ld", size);
            // 积极应答 213, 文件大小
            send_reply(sess->bev, 213, text);
            return;
        }
    }

    // 文件不存在
    // 应答550,拒绝
    send_reply(sess->bev, 550, "Access is denied.");
}

/**
 * 文件下载命令
 * @param sess
 */
static void action_retr(Session *sess)
{
    if (!sess) return;
    RETR_action(sess);
}

/**
 * 文件上传
 * @param sess
 */
static void action_stor(Session *sess)
{
    if (!sess) return;
    STOR_action(sess);
}

/**
 * 文件上传 - 文件名由服务器决定，并且文件名唯一
 * @param sess
 */
static void action_stou(Session *sess)
{
    if (!sess) return;
    STOU_action(sess);
}

static void action_appe(Session *sess)
{
    if (!sess) return;
    APPE_action(sess);
}
/**
 * 文件续传 - 设置起始点
 * @param sess
 * @note REST命令后面必须紧接着STOR或RETR命令，才有效
 */
static void action_rest(Session *sess)
{
    if (!sess) return;

    long int offset;
    char *stop;
    offset = strtol(sess->cmd_text, &stop, 10);
    if (offset == LONG_MIN || offset == LONG_MAX || (stop != NULL && *stop != '\0')) {
        // 参数指定的起始位置值溢出 或包含非数字字符
        send_reply(sess->bev, 501, "Syntax error in parameters or argument.");
        return;
    }

    sess->offset = offset;
    send_reply(sess->bev, 350, "Requested file action pending further information.");
}

/**
 * 重新命名开始命令
 * @param sess
 */
static void action_rnfr(Session *sess)
{
    if (!sess) return;

    if (!dir_exist(sess->dir, sess->cmd_text)) {
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return;
    }

    // TODO 尝试获得文件锁

    // 保存旧的文件名
    strncpy(sess->rename_old_path, sess->cmd_text, sizeof(sess->rename_old_path));

    // 请求重命名文件名 （RNTO命令）
    send_reply(sess->bev, 350, "Requested file action pending further information.");
}

/**
 * 重命名为命令
 * @param sess
 */
static void action_rnto(Session *sess)
{
    if (!sess) return;

    if (strlen(sess->rename_old_path) == 0) {
        send_reply(sess->bev, 503, "Bad sequence of commands. Need RNFR first.");
        return;
    }

    // 判断新文件名是否已存在
    if (dir_exist(sess->dir, sess->cmd_text)) {
        send_reply(sess->bev, 553, "Requested action not taken; File name not allowed.");
        return;
    }

    // 取得新的文件名
    strncpy(sess->rename_new_path, sess->cmd_text, sizeof(sess->rename_new_path));
    // 重命名
    if (dir_rename(sess->dir, sess->rename_old_path, sess->rename_new_path) < 0) {
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return;
    }

    send_reply(sess->bev, 250, "Rename complete. ");
    // TODO 释放锁

    // 清空从命名文件路径信息
    strcpy(sess->rename_old_path, "\0");
    strcpy(sess->rename_new_path, "\0");
}

/**
 * 删除普通文件
 * @param sess
 */
static void action_dele(Session *sess)
{
    if (!sess) return;

    // 获取文件物理路径
    char file_path_phy[PATH_MAX];
    if (dir_getPhysicalPath(sess->dir, sess->cmd_text, file_path_phy, sizeof(file_path_phy)) < 0) {
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable."); /* 没有找到文件 */
        return ;
    }

    // TODO 获得文件锁

    // 判断是否为普通文件
    if (!dir_isRegFile(file_path_phy)) { // 非普通文件，无法通过DELE命令删除
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return;
    }

    // 测试文件权限
    if (access(file_path_phy, W_OK) < 0) { // 对文件不具有写权限
        err_ret("access error on %s", file_path_phy);
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return;
    }
    // 删除文件
    if (unlink(file_path_phy) < 0) { // 删除文件失败
        err_ret("unlink error on %s", file_path_phy);
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return;
    }

    send_reply(sess->bev, 250,"Delete complete.");
}

/**
 * 删除空目录
 * @param sess
 * @note 删除非空目录，用RMDA命令. RMDA是由客户端实现，通过递归删除空目录和文件
 * @see RMDA
 */
static void action_rmd(Session *sess)
{
    if (!sess) return;

    // 获取文件物理路径
    char file_path_phy[PATH_MAX];
    if (dir_getPhysicalPath(sess->dir, sess->cmd_text, file_path_phy, sizeof(file_path_phy)) < 0) {
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable."); /* 没有找到文件 */
        return ;
    }

    // TODO 获得文件锁

    // 测试是否具有写权限
    if (access(file_path_phy, W_OK) < 0) {
        // 对文件不具有写权限
        err_ret("access error on file %s", file_path_phy);
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return;
    }
    // 删除空目录
    if (rmdir(file_path_phy) < 0) { // 删除失败
        err_ret("rmdir error on %s", file_path_phy);
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return;
    }

    send_reply(sess->bev, 250,"Delete complete.");
}

/**
 * 新建目录
 * @param sess
 */
static void action_mkd(Session *sess)
{
    if (!sess || !sess->bev) return;

    // 获取文件物理路径
    char file_path_phy[PATH_MAX];
    if (dir_getPhysicalPath(sess->dir, sess->cmd_text, file_path_phy, sizeof(file_path_phy)) < 0) { /* 获取指定路径出错 */
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return ;
    }

    // 创建目录
    if (mkdir(file_path_phy, 0775) < 0) {  /* 创建目录失败  */
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        err_ret("mkdir error on %s", file_path_phy);
        return;
    }

    char msg[PATH_MAX];
    snprintf(msg, sizeof(msg), "\"%s\" created.", sess->cmd_text);
    send_reply(sess->bev, 257,msg);
}

static void action_pwd(Session *sess)
{
    if (!sess || !sess->bev) return;
    char dir_path[PATH_MAX];
    char msg[PATH_MAX];

    // i.g. 257 /
    dir_getCWD(sess->dir, dir_path, sizeof(dir_path));
    snprintf(msg, sizeof(msg), "\"%s\" is current dir.", dir_path);
    send_reply(sess->bev, 257, msg);
}

static void action_cwd(Session *sess)
{
    if (!sess || !sess->bev) return;
    if (dir_chdir(sess->dir, sess->cmd_text) < 0) { /* error */
        send_reply(sess->bev, 550, "No such file or directory.");
        return;
    }

    char buf[1024];
    char msg[1024];
    // i.g. 250 /home/martin/ftp/
    dir_getCWD(sess->dir, buf, sizeof(buf));
    snprintf(msg, sizeof(msg), "directory success changed to %s", buf);
    send_reply(sess->bev, 250, msg);
}

static void action_cdup(Session *sess)
{
    if (!sess || !sess->bev) return;
    dir_print(sess->dir);
    if (dir_chdir(sess->dir, "..") < 0) { /* error */
        send_reply(sess->bev, 550, "No such file or directory.");
        return;
    }
    dir_print(sess->dir);

    char buf[1024];
    char msg[1024];
    dir_getCWD(sess->dir, buf, sizeof(buf));
    snprintf(msg, sizeof(msg), "directory success changed to %s", buf);
    send_reply(sess->bev, 250, dir_getCWD(sess->dir, buf, sizeof(buf)));
}

/**
 * @note Request: PORT 192,168,216,1,194,151
 * 地址格式: h1,h2,h3,h4,p1,p2
 */
static void action_port(Session *sess)
{
    if (!sess || !sess->bev) return;
    size_t i, j, k;
    size_t len = strlen(sess->cmd_text);
    int seg[6] = { 0 };
    char buf[20] = { 0 };
    int errflg = 0;

    // 从命令内容提取客户端指定IP地址信息
    for (i = 0, j = 0, k = 0; i < len + 1; i++) {
        if (sess->cmd_text[i] == ',' || sess->cmd_text[i] == '\0') {
            bzero(buf, sizeof(buf));
            strncpy(buf, &sess->cmd_text[j], i - j);
            seg[k] = atoi(buf);
            if (seg[k] < 0 || seg[k] > 255) { // 非法ip地址&端口对应数字
                errflg = 1;
                break;
            }
            k++;
            j = i + 1;
        }
        else if (!isdigit(sess->cmd_text[i])) { // 非数字字符
            errflg = 1;
            break;
        }
    }

    sprintf(sess->client_ip, "%d.%d.%d.%d", seg[0], seg[1], seg[2], seg[3]);
    sess->client_data_port = seg[4] * 256 + seg[5];
    printf("client data channel: %s:%d\n", sess->client_ip, sess->client_data_port);

    if (errflg) {
        send_reply(sess->bev, 501, "Syntax error in parameters or argument.");
        return;
    }
    send_reply(sess->bev, 200, "Okay.");
}

#include <event2/listener.h>

void data_conn_listen_cb(struct evconnlistener *listener, int fd,struct sockaddr *sa, int socklen,void *arg)
{

}

int listenPASV(Session *sess)
{
    struct evconnlistener *data_conn_listener;
    struct sockaddr_in sin;

    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    evutil_inet_pton(AF_INET, sess->serv_ip, &sin.sin_addr.s_addr);
    sin.sin_port = htons(sess->serv_data_port);

    data_conn_listener = evconnlistener_new_bind(
            bufferevent_get_base(sess->bev),
            data_conn_listen_cb,
            NULL,
            LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
            -1,
            (struct sockaddr *)&sin,
            sizeof(sin) );
    if (!data_conn_listener) {
        err_msg("data conn: evconnlistener_new_bind error");
        return -1;
    }

    evconnlistener_enable(data_conn_listener);
}

static void action_pasv(Session *sess)
{
    if (!sess || !sess->bev) return;

}

/**
 * 建立到用户的PORT模式数据通道
 * @param sess FTP连接会话
 * @return 连接结果
 * @note 所有PORT模式的数据连接，FTP服务器端共享一个数据端口sess->serv_data_port（FTP标准值20）。
 * 注意：PORT模式下，FTP服务器属于数据连接的客户端，FTP客户端是数据连接的服务器端
 */
int connect_PORT(Session *sess, bufferevent_data_cb readcb,
                 bufferevent_data_cb writecb, bufferevent_event_cb eventcb)
{
    if (!sess || !sess->bev) return -1;

    // 检查输入的ip地址信息
    if (strlen(sess->serv_ip) < 1 || sess->serv_data_port < 0) {
        err_msg("connect_PORT failed: server ip or port is invalid");
        return -1;
    }
    if (strlen(sess->client_ip) < 1 || sess->client_data_port < 0) {
        err_msg("connect_PORT failed: client ip or port is invalid");
        return -1;
    }

    // 新建socket并bind本地ip及数据端口
    int sockfd = socket_client_create_bind(sess->serv_ip, sess->serv_data_port);

    // 设置数据通道要连接的对端用户地址信息
    struct sockaddr_in peer;
    bzero(&peer, sizeof(peer));
    peer.sin_family = AF_INET;
    evutil_inet_pton(AF_INET, sess->client_ip, &peer.sin_addr.s_addr);
    peer.sin_port = htons(sess->client_data_port);

    // 创建数据连接通道
    struct bufferevent *data_bev;
    data_bev = bufferevent_socket_new(
            bufferevent_get_base(sess->bev),
            sockfd,  /* sockfd >= 0, trigger EV_WRITE when connect; sockfd < 0, trigger BEV_CONNECTED when connect */
            BEV_OPT_CLOSE_ON_FREE);
    if (!data_bev) {
        err_msg("create bufferevent error for data conn");
        return -1;
    }

    // 设置回调与权限
    bufferevent_setcb(data_bev, readcb, writecb, eventcb, sess);
    bufferevent_enable(data_bev, EV_READ | EV_WRITE);

    // 设置超时
    struct timeval readtv = {60, 0};
    bufferevent_set_timeouts(data_bev, &readtv, NULL);
    // 数据通道连接客户
    if (bufferevent_socket_connect(data_bev, (struct sockaddr *)&peer, sizeof(peer)) < 0) {
        err_msg("connect client data port error");
        bufferevent_free(data_bev);
        return -1;
    }

    // 保存监听数据通道的bufferevent到Session
    sess->data_bev = data_bev;

    printf("Try to connect client[%s:%d] using PORT mode\n", sess->client_ip, sess->client_data_port);
    return 0;
}

static void action_list(Session *sess)
{
    if (!sess || !sess->bev) return;
    LIST_action(sess);
}

static void action_type(Session *sess)
{
    if (!sess || !sess->bev) return;

    switch (sess->cmd_text[0]) {
        case 'I':
            // Turn the binary flag on.
            sess->binary_flag = 1;
            send_reply(sess->bev, 200, "Switching to binary mode.");
            break;
        case 'A':
            // Turn the binary flag off.
            sess->binary_flag = 0;
            send_reply(sess->bev, 200, "Switching to ASCII mode.");
            break;
        default:
            // 不支持的数据类型
            send_reply(sess->bev, 504, "Data type denied.");
    }
}

static void action_syst(Session *sess)
{
    if (!sess || !sess->bev) return;

    // 215 Unix Type: L8
    send_reply(sess->bev, 215, "Unix Type: L8");
}

static void action_quit(Session *sess)
{
    if (!sess || !sess->bev) return;

    // 221 Bye.
    send_reply(sess->bev, 221, "Bye.");
}
